/* Copyright(C) 2008, Swedish Institute of Computer Science */

%   File         : minizinc.pl
%   Author       : agren
%   Purpose      : Predicates to handle MiniZinc programs.

:- module(minizinc, [mzn_load_file/2,
		     mzn_load_file/3,
		     mzn_load_model/2,
		     mzn_load_model/3,
		     mzn_run_file/1,
		     mzn_run_file/2,
		     mzn_run_model/1,
		     mzn_run_model/2,
		     mzn_to_fzn/2,
		     mzn_to_fzn/3,
		     mzn_to_fzn_dir/1]).

:- use_module(library(file_systems)).
:- use_module(library(lists)).
:- use_module(library(process)).
:- use_module(library(system)).
:- use_module(library(types)).

:- use_module(library('zinc/zinc_utils')).
:- use_module(library('zinc/flatzinc')).


mzn_run_file(MznFile) :-
	mzn_run_file(MznFile, [], mzn_run_file(MznFile)-0).

mzn_run_file(MznFile, Options) :-
	mzn_run_file(MznFile, Options, mzn_run_file(MznFile, Options)-2).

mzn_run_file(MznFile, Options, GoalArg) :-
	call_cleanup((mzn2fzn(MznFile, Options, GoalArg, FznOptions,
			      FznStream, Mzn2Fzn, StdErr, CleanUp),
		      fzn_run_stream(FznStream, FznOptions, GoalArg)),
		     (safe_process_wait(Mzn2Fzn, ExitCode),
		      handle_exit(ExitCode, StdErr),
		      safe_close(FznStream),
		      safe_close(StdErr),
		      cleanup(CleanUp))).


mzn_run_model(MznModel) :-
	mzn_run_model(MznModel, [], mzn_run_model(MznModel)-0).

mzn_run_model(MznModel, Options) :-
	mzn_run_model(MznModel, Options, mzn_run_model(MznModel, Options)-2).

mzn_run_model(MznModel, Options, GoalArg) :-
	model_to_tmp(MznModel, MznFile),
	call_cleanup(mzn_run_file(MznFile, Options, GoalArg),
		     delete_file(MznFile)).


mzn_load_file(MznFile, FznState) :-
	mzn_load_file(MznFile, [], mzn_load_file(MznFile, FznState)-0,
		      FznState).

mzn_load_file(MznFile, Options, FznState) :-
	mzn_load_file(MznFile, Options,
		      mzn_load_file(MznFile, Options, FznState)-2, FznState).
	
mzn_load_file(MznFile, Options, GoalArg, FznState) :-
	call_cleanup((mzn2fzn(MznFile, Options, GoalArg, _FznOptions,
			      FznStream, Mzn2Fzn, StdErr, CleanUp),
		      fzn_load_stream(FznStream, FznState)),
		     (safe_process_wait(Mzn2Fzn, ExitCode),
		      handle_exit(ExitCode, StdErr),
		      safe_close(FznStream),
		      safe_close(StdErr),
		      cleanup(CleanUp))),
	(   member(post(false), Options)
	->  true
	;   fzn_post(FznState)
	),
	mzn_variables(Options, FznState).

mzn_load_model(MznModel, FznState) :-
	GoalArg = mzn_load_model(MznModel, FznState)-0,
	mzn_load_model(MznModel, [], GoalArg, FznState).

mzn_load_model(MznModel, Options, FznState) :-
	GoalArg = mzn_load_model(MznModel, Options, FznState)-2,
	mzn_load_model(MznModel, Options, GoalArg, FznState).

mzn_load_model(MznModel, Options, GoalArg, FznState) :-
	model_to_tmp(MznModel, MznFile),
	call_cleanup(mzn_load_file(MznFile, Options, GoalArg, FznState),
		     delete_file(MznFile)).

mzn_to_fzn(MznFile, FznFile) :-
	mzn_to_fzn(MznFile, [], mzn_to_fzn(MznFile, FznFile)-0, FznFile).

mzn_to_fzn(MznFile, Options, FznFile) :-
	mzn_to_fzn(MznFile, Options, mzn_to_fzn(MznFile, Options, FznFile)-2,
		   FznFile).

mzn_to_fzn_dir(D) :-
	file_member_of_directory(D, '*.mzn', _, MznFile),
	atom_concat(File, '.mzn', MznFile),
	atom_concat(File, '.fzn', FznFile),
	mzn_to_fzn(MznFile, FznFile),
        fail.
mzn_to_fzn_dir(_).


mzn_to_fzn(MznFile, Options, GoalArg, FznFile) :-
	zinc_open(FznFile, write, '.fzn', FznOutput),
	call_cleanup((mzn2fzn(MznFile, Options, GoalArg, _FznOptions, FznInput,
			      Mzn2Fzn, StdErr, CleanUp),
		      (
			get_code_rec(FznInput, C),
			put_code(FznOutput, C),
			fail
		      ;
			true
		      )),
		     (
		       safe_process_wait(Mzn2Fzn, ExitCode),
		       handle_exit(ExitCode, StdErr),
		       safe_close(FznInput),
		       safe_close(FznOutput),
		       safe_close(StdErr),
		       cleanup(CleanUp)
		     )).

model_to_tmp(MznModel0, MznFile) :-
	append(MznModel0, MznModel),
	open(temp('sptmp.mzn'), write, MznStream,
	     [if_exists(generate_unique_name)]),
	stream_property(MznStream, file_name(MznFile)),
	(
	  member(C, MznModel),
	  put_code(MznStream, C),
	  fail
	;
	  close(MznStream)
	).

get_code_rec(S, _) :-
	peek_code(S, -1), !,
	fail.
get_code_rec(S, C) :-
	get_code(S, C).
get_code_rec(S, C) :-
	get_code_rec(S, C).

mzn2fzn(MznFile0, Options, GoalArg, FznOptions, FznStream, Mzn2FznProc,
	StdErr,	CleanUp) :-
	zinc_options(Options, GoalArg, Options1),
	include(mzn_option, Options1, MznOptions),
	exclude(mzn_option, Options1, FznOptions),
	mzn2fzn_path(GoalArg, Mzn2Fzn),
	absolute_file_name(library('zinc/globals'), LibDir,
			   [file_type(directory), access(exist)]),
	absolute_file_name(MznFile0, MznFile, [extensions(['.mzn']),
					       access(read)]),
	absolute_file_name((.), MznFileDir, [relative_to(MznFile),
					     file_type(directory)]),
	mzn_parameters(MznOptions, DataFiles1, CleanUp),
	mzn_data_files(MznOptions, DataFiles2),
	mzn_search_dirs(MznOptions, SearchDirs),
	append([['--no-output-pred-decls', '--output-to-stdout', '--search-dir',
		 file(LibDir), '--search-dir', MznFileDir],
		DataFiles1, DataFiles2, SearchDirs, [file(MznFile)]], Args),
	process_create(Mzn2Fzn, Args, [stdin(null),	
				       stdout(pipe(FznStream)), 
				       stderr(pipe(StdErr)),
				       process(Mzn2FznProc)]).

mzn2fzn_path(GoalArg, Mzn2Fzn) :-
	environ('MZN2FZN', Mzn2Fzn0), !,
	on_exception(_,
		     absolute_file_name(Mzn2Fzn0, Mzn2Fzn,
					[file_type(executable), access(execute)]),
		     (   Goal-_ = GoalArg,
			 illarg(existence(mzn2fzn, 0, ''), Goal, 0)
		     )).
mzn2fzn_path(GoalArg, Mzn2Fzn) :-	
	on_exception(_,
		     absolute_file_name(path(mzn2fzn), Mzn2Fzn,
					[file_type(executable), access(execute)]),
		     (   Goal-_ = GoalArg,
			 illarg(existence(mzn2fzn, 0, ''), Goal, 0)
		     )).

:- multifile user:generate_message_hook/3.
user:generate_message_hook(Error) -->
	{Error = existence_error(_, _, mzn2fzn, _, _)}, !,
	['Existence error'-[], nl,
	 'mzn2fzn was not found or could not be run'-[], nl].

mzn_option(data_file(_)).
mzn_option(parameters(_)).
mzn_option(variables(_)).
mzn_option(post(_)).
mzn_option('--search-dir'(_)).

safe_process_wait(Process, ExitCode) :-
	(   is_process(Process)
	->  process_wait(Process, ExitCode)
	;   true
	).

handle_exit(exit(0), _) :- !.
handle_exit(_, StdErr) :-
	read_all_lines(StdErr, ErrorLines),
	append(["mzn2fzn error: "|ErrorLines], Error),
	atom_codes(Message, Error),
	illarg(system(Message), mzn2fzn, 0).

read_all_lines(Stream, Lines) :-
	read_line(Stream, Line),
	(   Line = end_of_file
	->  Lines = []
	;   read_all_lines(Stream, Ls),
	    Lines = [Line|Ls]
	).

mzn_search_dirs([], []).
mzn_search_dirs(['--search-dir'(D)|Os], ['--search-dir', D|Ds]) :- !,
	mzn_search_dirs(Os, Ds).
mzn_search_dirs([_|Os], Ds) :-
	mzn_search_dirs(Os, Ds).

mzn_data_files([], []).
mzn_data_files([data_file(File0)|Os], ['--data', File|Ds]) :- !,
	absolute_file_name(File0, File),
	mzn_data_files(Os, Ds).
mzn_data_files([_|Os], Ds) :-
	mzn_data_files(Os, Ds).
	

mzn_parameters([], [], []).
mzn_parameters([parameters(Pars)|Os], ['--data', DatFile|Ds],
	       [delete_file(DatFile)|Cs]) :- !,
	open(temp('sptmp.dat'), write, DatStream,
	     [if_exists(generate_unique_name)]),
	call_cleanup((stream_property(DatStream, file_name(DatFile)),
		      write_parameters(Pars, DatStream)),
		     close(DatStream)),
	mzn_parameters(Os, Ds, Cs).
mzn_parameters([_|Os], Ds, Cs) :-
	mzn_parameters(Os, Ds, Cs).

write_parameters([], _).
write_parameters([P|Ps], Stream) :-
	write(Stream, P),
	write(Stream, ';'),
	nl(Stream),
	write_parameters(Ps, Stream).

mzn_variables([], _).
mzn_variables([variables(Vars)|Os], State) :- !,
	bind_variables(Vars, State),
	mzn_variables(Os, State).
mzn_variables([_|Os], State) :-
	mzn_variables(Os, State).

bind_variables([], _).
bind_variables([Id=Var|Vars], State) :-
	fzn_identifier(State, Id, Var),
	bind_variables(Vars, State).

cleanup([]) :- !.
cleanup([G|Gs]) :-
	call(G),
	cleanup(Gs).
